package com.mc.lp.database.service.handler.user;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import javax.annotation.Resource;

import org.springframework.util.CollectionUtils;

import com.google.visualization.datasource.base.TypeMismatchException;
import com.google.visualization.datasource.datatable.ColumnDescription;
import com.google.visualization.datasource.datatable.DataTable;
import com.google.visualization.datasource.datatable.value.ValueType;
import com.google.visualization.datasource.render.JsonRenderer;
import com.mc.lp.core.util.CollectionUtil;
import com.mc.lp.database.cache.CacheServiceType;
import com.mc.lp.database.cache.DBCacheService;
import com.mc.lp.database.cache.SubjectCache;
import com.mc.lp.database.entity.type.RecordType;
import com.mc.lp.database.entity.type.SchoolGrade;
import com.mc.lp.database.entity.type.StrengthWeaknessType;
import com.mc.lp.database.exception.DataNotFoundException;
import com.mc.lp.database.service.IAttachmentService;
import com.mc.lp.database.service.handler.AbstractFormHandler;
import com.mc.lp.database.service.handler.constant.FormRequestType;
import com.mc.lp.database.service.handler.util.AggregatedGrades;
import com.mc.lp.database.service.handler.util.GradeAggregator;
import com.mc.lp.service.AcademicRecordService;
import com.mc.lp.service.MicroCampusEntityService;
import com.mc.lp.service.RecommendationService;
import com.mc.lp.service.SportsService;
import com.mc.lp.shared.util.constants.ResponseStatus;
import com.mc.lp.shared.vo.AcademicRecordVO;
import com.mc.lp.shared.vo.AttachmentVO;
import com.mc.lp.shared.vo.FileVo;
import com.mc.lp.shared.vo.IValueObject;
import com.mc.lp.shared.vo.KeyValue;
import com.mc.lp.shared.vo.RecommendationVO;
import com.mc.lp.shared.vo.SearchCriteriaVO;
import com.mc.lp.shared.vo.ServiceResponse;
import com.mc.lp.shared.vo.StrengthWeaknessVo;
import com.mc.lp.shared.vo.StudentProfileVO;
import com.mc.lp.shared.vo.user.CommonGridVo;
import com.mc.lp.shared.vo.user.MarksListVO;
import com.mc.lp.shared.vo.user.MarksVO;
import com.mc.lp.shared.vo.user.OverallGraphVO;
import com.mc.lp.shared.vo.user.OverallGridVO;
import com.mc.lp.shared.vo.user.SubjectListVO;

/**
 * user dashboard handler
 * 
 * @author deepak
 * 
 * 
 */
public class UserDashboardHandler extends AbstractFormHandler<IValueObject, IValueObject> {

	@Resource
	private AcademicRecordService academicRecordService;

	@Resource
	private SportsService sportsService;

	@Resource
	private MicroCampusEntityService microCampusEntityService;

	@Resource
	private RecommendationService recommendationService;

	@Resource
	private IAttachmentService attachmentService;

	@Override
	public DBCacheService getCacheService() {
		// TODO Auto-generated method stub
		return super.getCacheService();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.mc.lp.database.service.handler.constant.IFormHandler#
	 * getRequestHandlingType()
	 */
	@Override
	public HashSet<FormRequestType> getRequestHandlingType() {
		HashSet<FormRequestType> handledRequest = new HashSet<FormRequestType>();
		handledRequest.add(FormRequestType.USER_DASHBOARD_OVERALL_GRID);
		handledRequest.add(FormRequestType.USER_DASHBOARD_OVERALL_GRAPH);
		handledRequest.add(FormRequestType.USER_DASHBOARD_OVERALL_RECOMMENDATION);
		handledRequest.add(FormRequestType.USER_DASHBOARD_OVERALL_STRENGTH);
		handledRequest.add(FormRequestType.USER_DASHBOARD_GET_STUDENT_DATA);
		handledRequest.add(FormRequestType.USER_DASHBOARD_ADVANCED_GRAPH);
		return handledRequest;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.mc.lp.database.service.handler.AbstractFormHandler#serveRequest(com
	 * .mc.lp.database.service.handler.constant.FormRequestType,
	 * com.mc.lp.shared.vo.IValueObject)
	 */
	@Override
	public IValueObject serveRequest(FormRequestType requestType, IValueObject object) throws Exception {
		switch (requestType) {
		case USER_DASHBOARD_OVERALL_GRID:
			return getOverallGrid(requestType, (SearchCriteriaVO) object);
		case USER_DASHBOARD_OVERALL_GRAPH:
			return getOverallGraphData(requestType, (SearchCriteriaVO) object);
		case USER_DASHBOARD_GET_STUDENT_DATA:
			return getStudentDataFromParentUserId(requestType, (SearchCriteriaVO) object);
		case USER_DASHBOARD_OVERALL_RECOMMENDATION:
			return getRecommendationsForStudent(requestType, (SearchCriteriaVO) object);
		case USER_DASHBOARD_ADVANCED_GRAPH:
			return getAdvancedGraphData(requestType, (SearchCriteriaVO) object);
		case USER_DASHBOARD_OVERALL_STRENGTH:
			return getStrengthAndWeaknessData(requestType, (SearchCriteriaVO) object);
		default:
			return null;
		}
	}

	private IValueObject getStrengthAndWeaknessData(FormRequestType requestType, SearchCriteriaVO object) {
		List<Integer> gradeValueList = getGradesByPageNumAndLimit(object);
		List<AcademicRecordVO> acadList = academicRecordService.getAcademicsByGrades(object.getStudentEntityId(), gradeValueList);

		ServiceResponse<CommonGridVo<StrengthWeaknessVo>> serviceResponse = new ServiceResponse<CommonGridVo<StrengthWeaknessVo>>();
		List<KeyValue> gradeList = new ArrayList<KeyValue>();
		CommonGridVo<StrengthWeaknessVo> commonGridVo = new CommonGridVo<StrengthWeaknessVo>();
		SubjectCache subjectCache = (SubjectCache) getCacheService().getService(CacheServiceType.SUBJECT);

		// new logic implementation
		ArrayList<AggregatedGrades> aggregatedGrades = new ArrayList<AggregatedGrades>();
		GradeAggregator aggregator = new GradeAggregator(acadList, aggregatedGrades);
		aggregator.aggregate();

		if (!CollectionUtils.isEmpty(aggregatedGrades))
			for (Entry<Integer, ArrayList<AggregatedGrades>> aggregatedGrade : aggregator.getGradeMap().entrySet()) {
				
				ArrayList<KeyValue> strength = new ArrayList<KeyValue>();
				ArrayList<KeyValue> weakness = new ArrayList<KeyValue>();
				
				//loop each grade aggregate
				for (AggregatedGrades grades : aggregatedGrade.getValue()) {
					if (grades.getAverageWeightage() > StrengthWeaknessType.STRENGTH.getMinimumWeightage()) {
						strength.add(new KeyValue(subjectCache.getEntityById(grades.getSubjectId()).getSubjectName(), null));
					} else if (grades.getAverageWeightage() > 0 && grades.getAverageWeightage() <= StrengthWeaknessType.WEAKNESS.getMinimumWeightage()) {
						weakness.add(new KeyValue(subjectCache.getEntityById(grades.getSubjectId()).getSubjectName(), null));
					}
				}
				StrengthWeaknessVo strengthWeaknessVo = new StrengthWeaknessVo();
				strengthWeaknessVo.setStrength(strength);
				strengthWeaknessVo.setWeakness(weakness);
				gradeList.add(new KeyValue(SchoolGrade.getSchoolGradeName(aggregatedGrade.getKey()), strengthWeaknessVo));
			}

		commonGridVo.setGradeList(gradeList);
		serviceResponse.setStatus(ResponseStatus.SUCCESS);
		serviceResponse.setResult(Arrays.asList(commonGridVo));
		return serviceResponse;

	}

	private IValueObject getAdvancedGraphData(FormRequestType requestType, SearchCriteriaVO object) {
		// DV BP-1 please send the correct data on clicking advance graph first
		// time from main academic page....
		ServiceResponse<OverallGraphVO> serviceResponse = new ServiceResponse<OverallGraphVO>();
		SubjectCache subjectCache = (SubjectCache) getCacheService().getService(CacheServiceType.SUBJECT);

		List<AcademicRecordVO> acadList = academicRecordService.getAcademicsBySearchCriteriaForAdvancedGraph(object);

		// key as Subject id and Year as internal key with value MarksList Vo
		// for terms
		TreeMap<Integer, TreeMap<Long, MarksListVO>> yearMap = new TreeMap<Integer, TreeMap<Long, MarksListVO>>(Collections.reverseOrder());

		TreeMap<Integer, TreeMap<Integer, List<MarksVO>>> termMap = new TreeMap<Integer, TreeMap<Integer, List<MarksVO>>>(Collections.reverseOrder());

		List<String> headerList = new ArrayList<String>();
		headerList.add("Class");

		DataTable table = null;

		UserDashboardHandlerSupport support = new UserDashboardHandlerSupport();

		if (object.getSelectedTerm() != null && object.getSelectedTerm().length > 0) {

			Set<Long> subjectSet = new HashSet<Long>();

			for (AcademicRecordVO academicRecordVO : acadList) {
				// to add all subjects if from UI no subjects is selected
				if (object.getSelectedSubjectList() != null && !(object.getSelectedSubjectList().length > 0)) {
					subjectSet.add(academicRecordVO.getSubjectId());
				}

				if (!termMap.containsKey(academicRecordVO.getGradeId())) {
					termMap.put(academicRecordVO.getGradeId(), new TreeMap<Integer, List<MarksVO>>());
				}
				Map<Integer, List<MarksVO>> termMarksMap = termMap.get(academicRecordVO.getGradeId());

				if (!termMarksMap.containsKey(academicRecordVO.getTermId())) {
					termMarksMap.put(academicRecordVO.getTermId(), new ArrayList<MarksVO>());
				}

				MarksVO marksVo = new MarksVO(academicRecordVO.getMarks(), Integer.toString(academicRecordVO.getTermId()), academicRecordVO.getMaxMarks(), academicRecordVO.getSubjectId());

				termMarksMap.get(academicRecordVO.getTermId()).add(marksVo);
			}

			if (!subjectSet.isEmpty()) {
				try {
					object.setSelectedSubjectList((CollectionUtil.convertLongArraytoStringArray(subjectSet.toArray(new Long[subjectSet.size()]))));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}

			table = support.processDataTableForTerm(termMap, object, subjectCache);

		} else {
			for (AcademicRecordVO academicRecordVO : acadList) {

				if (!yearMap.containsKey(academicRecordVO.getGradeId())) {
					yearMap.put(academicRecordVO.getGradeId(), new TreeMap<Long, MarksListVO>());
				}
				Map<Long, MarksListVO> subjectMarksMap = yearMap.get(academicRecordVO.getGradeId());

				if (!subjectMarksMap.containsKey(academicRecordVO.getSubjectId())) {
					subjectMarksMap.put(academicRecordVO.getSubjectId(), new MarksListVO());
				}

				MarksVO marksVo = new MarksVO(academicRecordVO.getMarks(), Integer.toString(academicRecordVO.getTermId()), academicRecordVO.getMaxMarks());

				subjectMarksMap.get(academicRecordVO.getSubjectId()).getMarks().add(marksVo);
			}
			table = support.generateDataTableForAdvanceGraph(yearMap, subjectCache);

		}

		OverallGraphVO graphVo = new OverallGraphVO();
		serviceResponse.setStatus(ResponseStatus.SUCCESS);
		serviceResponse.setResult(Arrays.asList(graphVo));
		serviceResponse.setObject(JsonRenderer.renderDataTable(table, true, true, true).toString());

		return serviceResponse;

	}

	private IValueObject getRecommendationsForStudent(FormRequestType requestType, SearchCriteriaVO object) {
		ServiceResponse<RecommendationVO> serviceResponse = new ServiceResponse<RecommendationVO>();
		List<RecommendationVO> recommendationVOS;
		try {
			recommendationVOS = recommendationService.getRecommendationBySearchCriteria(object);
			serviceResponse.setResult(recommendationVOS);
			serviceResponse.setExtraInfo(new KeyValue("class", SchoolGrade.getSchoolGradeName(object.getGradeId()))); // send
																														// recommendation
																														// year
																														// along
																														// with
																														// extra
																														// into
			serviceResponse.setStatus(ResponseStatus.SUCCESS);
		} catch (DataNotFoundException e) {
			serviceResponse.setStatus(ResponseStatus.NOT_FOUND);
		} catch (Exception e) {
			serviceResponse.setStatus(ResponseStatus.FAILED);
		}
		return serviceResponse;
	}

	private IValueObject getStudentDataFromParentUserId(FormRequestType requestType, SearchCriteriaVO object) {
		ServiceResponse<StudentProfileVO> response = new ServiceResponse<StudentProfileVO>();
		List<StudentProfileVO> studentProfileList = microCampusEntityService.getChildFromParentId(object.getUserId());
		
		response.setResult(studentProfileList);
		response.setStatus(ResponseStatus.SUCCESS);
		return response;
	}

	private IValueObject getOverallGrid(FormRequestType requestType, SearchCriteriaVO object) {

		ServiceResponse<OverallGridVO> serviceResponse = new ServiceResponse<OverallGridVO>();
		List<Integer> gradeValueList = getGradesByPageNumAndLimit(object);

		if (gradeValueList == null) {
			serviceResponse.setStatus(ResponseStatus.NOT_FOUND);
			return serviceResponse;
		}
		List<AcademicRecordVO> acadList = academicRecordService.getAcademicsByGrades(object.getStudentEntityId(), gradeValueList);

		// key as Subject id and Year as internal key with value MarksList Vo
		// for terms
		Map<Long, HashMap<Long, MarksListVO>> map = new HashMap<Long, HashMap<Long, MarksListVO>>();
		SubjectCache subjectCache = (SubjectCache) getCacheService().getService(CacheServiceType.SUBJECT);
		for (AcademicRecordVO academicRecordVO : acadList) {

			if (!map.containsKey(academicRecordVO.getSubjectId())) {
				map.put(academicRecordVO.getSubjectId(), new HashMap<Long, MarksListVO>());
			}
			Map<Long, MarksListVO> yearMarksMap = map.get(academicRecordVO.getSubjectId());

			if (!yearMarksMap.containsKey(academicRecordVO.getYear())) {
				yearMarksMap.put(academicRecordVO.getYear(), new MarksListVO());
			}

			MarksVO marksVo = new MarksVO(academicRecordVO.getMarks(), Integer.toString(academicRecordVO.getTermId()));
			yearMarksMap.get(academicRecordVO.getYear()).getMarks().add(marksVo);
		}

		List<KeyValue> gradeList = new ArrayList<KeyValue>();
		// send me grade and attachment list
		for (Integer gradeId : gradeValueList) {
			List<AttachmentVO> voList = attachmentService.getAttachmentsByGrade(object.getStudentEntityId(), gradeId, RecordType.ACADEMICS, true);
			List<FileVo> files = new ArrayList<FileVo>(voList.size());
			for (AttachmentVO attachmentVO : voList) {
				files.add(attachmentVO.getFile());
			}
			gradeList.add(new KeyValue(SchoolGrade.getSchoolGradeName(gradeId), files.toArray(new FileVo[files.size()])));
		}

		OverallGridVO gridVo = new OverallGridVO();
		gridVo.setGradeList(gradeList);

		List<SubjectListVO> subjectList = new ArrayList<SubjectListVO>();

		for (Long subjectId : map.keySet()) {
			SubjectListVO subjects = new SubjectListVO();
			subjects.setName(subjectCache.getEntityById(subjectId).getSubjectName());
			List<MarksListVO> marksListVOs = new ArrayList<MarksListVO>();
			for (Long year : map.get(subjectId).keySet()) {
				marksListVOs.add(map.get(subjectId).get(year));
			}
			subjects.setMarksList(marksListVOs);
			subjectList.add(subjects);
		}
		gridVo.setSubjects(subjectList);
		serviceResponse.setStatus(ResponseStatus.SUCCESS);
		serviceResponse.setResult(Arrays.asList(gridVo));

		return serviceResponse;
	}

	private IValueObject getOverallGraphData(FormRequestType requestType, SearchCriteriaVO object) {
		// DV BP-1 Next and prev logic not working for user overall graph..
		// pagenum is not updating on clicking next and prev
		ServiceResponse<OverallGraphVO> serviceResponse = new ServiceResponse<OverallGraphVO>();

		List<Integer> gradeValueList = getGradesByPageNumAndLimit(object);
		if (gradeValueList == null) {
			serviceResponse.setStatus(ResponseStatus.NOT_FOUND);
			return serviceResponse;
		}
		List<AcademicRecordVO> acadList = academicRecordService.getAcademicsByGrades(object.getStudentEntityId(), gradeValueList);

		// key as Subject id and Year as internal key with value MarksList Vo
		// for terms
		Map<Integer, HashMap<Long, MarksListVO>> map = new TreeMap<Integer, HashMap<Long, MarksListVO>>(Collections.reverseOrder());
		List<String> headerList = new ArrayList<String>();
		headerList.add("Class");

		for (AcademicRecordVO academicRecordVO : acadList) {

			if (!map.containsKey(academicRecordVO.getGradeId())) {
				map.put(academicRecordVO.getGradeId(), new HashMap<Long, MarksListVO>());
			}
			Map<Long, MarksListVO> subjectMarksMap = map.get(academicRecordVO.getGradeId());

			if (!subjectMarksMap.containsKey(academicRecordVO.getSubjectId())) {
				subjectMarksMap.put(academicRecordVO.getSubjectId(), new MarksListVO());
			}

			MarksVO marksVo = new MarksVO(academicRecordVO.getMarks(), Integer.toString(academicRecordVO.getTermId()), academicRecordVO.getMaxMarks());
			subjectMarksMap.get(academicRecordVO.getSubjectId()).getMarks().add(marksVo);
		}

		OverallGraphVO graphVo = new OverallGraphVO();
		DataTable table = generateDataTable(map, null);

		serviceResponse.setStatus(ResponseStatus.SUCCESS);
		serviceResponse.setResult(Arrays.asList(graphVo));
		serviceResponse.setObject(JsonRenderer.renderDataTable(table, true, true, true).toString());

		return serviceResponse;
	}

	public DataTable generateDataTable(Map<Integer, HashMap<Long, MarksListVO>> map, List<String> terms) {
		// Create a data table.
		DataTable data = new DataTable();
		ArrayList<ColumnDescription> cd = new ArrayList<ColumnDescription>();
		cd.add(new ColumnDescription("Class", ValueType.TEXT, "Class"));

		SubjectCache subjectCache = (SubjectCache) getCacheService().getService(CacheServiceType.SUBJECT);
		// AP BP-1 Correct the logic for data table
		// Fill the data table.
		try {
			// Creating the Column Description for Charts
			for (Integer gradeId : map.keySet()) {
				List<Object> objects = new ArrayList<Object>();
				HashMap<Long, MarksListVO> innerMap = map.get(gradeId);
				objects.add(SchoolGrade.getSchoolGradeName(gradeId));

				for (Long subjectId : innerMap.keySet()) {
					String subjectname = subjectCache.getEntityById(subjectId).getSubjectName();
					cd.add(new ColumnDescription(subjectname, ValueType.NUMBER, subjectname));
				}
				break;
			}
			data.addColumns(cd);

			// Feeding-in the data for Charts
			for (Integer gradeId : map.keySet()) {

				List<Object> objects = new ArrayList<Object>();
				HashMap<Long, MarksListVO> innerMap = map.get(gradeId);
				objects.add(SchoolGrade.getSchoolGradeName(gradeId));

				for (Long subjectId : innerMap.keySet()) {
					MarksListVO vo = innerMap.get(subjectId);
					Double avgMarks = (double) vo.getAvgMarks();

					for (MarksVO marksVo : vo.getMarks()) {
						if (null != terms) {
							if (terms.contains(marksVo.getTerm())) {
								objects.add(marksVo.getMarks());
							}
						} else {
							avgMarks = (avgMarks + (float) marksVo.getMarks() / (float) marksVo.getMaxMarks() * 100);
						}
					}
					if (null == terms) {
						avgMarks = avgMarks / vo.getMarks().size();
						objects.add(avgMarks.longValue());
					}
				}
				data.addRowFromValues(objects.toArray(new Object[objects.size()]));
			}

		} catch (TypeMismatchException e) {
			// AP remove this line and throw exception or faliure in
			// serviceResponse :P
			System.out.println("Invalid type!");
		}
		return data;
	}

}